<?php
require_once($_SERVER["DOCUMENT_ROOT"] . "/include/connection_controller.php");
require_once("tree.php");

/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 8/13/14
 * Time: 4:57 PM
 */
class nextNode
{
    public $point;
    public $type;
    public $unit;
    public $parent;

    function __construct($point, $type, $unit, $parent)
    {
        $this->point = $point;
        $this->type = $type;
        $this->unit = $unit;
        $this->parent = $parent;
    }
}

class treeBuilder
{
    const WELDING = 0.1;
    protected $indexer = 0;
    protected $tree = null;
    protected $data;
    private $target;
    protected $leafs = array();
    protected $suggest_leafs = array();
    private $shortestDistance = null;
    private $closestPoint = null;

    function __construct($point_id, $target)
    {
        $this->target = $target;
        $children = array();
        $this->tree = new Tree();
        $this->indexer = 1;
        $this->data = ConnectionController::GetAllConnectionsWithUnits();
        $this->tree->addItem($this->indexer++, 0,
            array("id" => $point_id, "type" => "point")
        );
        $segments = array_filter($this->data->segments, function ($e) use ($point_id) {
            return $e->start_point == $point_id || $e->end_point == $point_id;
        });

        foreach ($segments as $segment) {
            $next_point = $segment->start_point == $point_id ? $segment->end_point : $segment->start_point;
            array_push($children, new nextNode(
                    $next_point,
                    "segment",
                    $segment->id, $this->addPointNode($segment, $next_point, 1))
            );
        }
        $cons = array_filter($this->data->connections, function ($e) use ($point_id) {
            return $e->id_point == $point_id && ($e->left_unit_type == "fiber" || $e->right_unit_type == "fiber");
        });

        foreach ($cons as $con) {
            $units = array();
            if ($con->left_unit_type == 'fiber') array_push($units, $con->left_unit);
            if ($con->right_unit_type == 'fiber') array_push($units, $con->right_unit);
            foreach ($units as $unit) {
                $fiber_unit = $this->data->fiber[$unit];
                array_push($children, $this->getFurtherConnectionForFiberUnit($fiber_unit, $point_id, 1));
            }
        }
        while (count($children) != 0) {
            $child = array_pop($children);
            $children = array_merge($children, $this->getFurtherConnections($child));
        }
    }

    function getFurtherConnections($node)
    {
        $children = array();
        if ($node->type == "segment") {
            $segment = $node->unit;
            $point = $node->point;
            $cons = array_filter($this->data->connections, function ($e) use ($segment, $point) {
                return $e->id_point == $point && ($e->left_unit == $segment || $e->right_unit == $segment);
            });
            if (count($cons) != 0) {
                $con = array_pop($cons);
                $next_segment = $this->data->segments[$con->left_unit == $segment ? $con->right_unit : $con->left_unit];
                $next_point = $next_segment->start_point == $con->id_point ? $next_segment->end_point : $next_segment->start_point;
                array_push($children, new nextNode($next_point, "segment", $next_segment->id, $this->addPointNode($next_segment, $next_point, $node->parent)));
                return $children;
            }
        }
        if ($node->type == "fiber" || $node->type == "segment") {
            $cons = array_filter($this->data->connections, function ($e) use ($node) {
                return $e->id_point == $node->point;
            });

            foreach ($cons as $con) {
                $units = array(array("type" => $con->left_unit_type, "id" => $con->left_unit),
                    array("type" => $con->right_unit_type, "id" => $con->right_unit));
                foreach ($units as $con_unit) {
                    if ($con_unit["type"] == 'fiber') {
                        $fiber_unit = $this->data->fiber[$con_unit["id"]];
                        // 1. пришло волокно и уходит в другое волокно
                        // или
                        // 2. пришел сегмент и в точке разделяется на волокна
                        if (($node->type == "fiber" && $fiber_unit->id_segment == $node->unit->id_segment && $fiber_unit->id_fiber == $node->unit->id_fiber)
                            || ($node->type == "segment" && $fiber_unit->id_segment == $node->unit)
                        ) {
                            $next_con_unit = $units[0]["id"] == $con_unit["id"] ? $units[1] : $units[0];
                            // волокно уходит в другое волокно
                            if ($next_con_unit["type"] == "fiber") {
                                $next_unit = $this->data->fiber[$next_con_unit["id"]];
                                $next_node = $this->getFurtherConnectionForFiberUnit($next_unit, $node->point, $node->parent);
                                $this->tree->addLocal($node->parent, self::WELDING);
                                array_push($children, $next_node);
                            } // волокно уходит в сплиттер
                            else {
                                $next_unit = $this->data->splitters[$next_con_unit["id"]];
                                $children = array_merge($children, $this->processSplitterConnection($next_unit, $node->point, $node->parent));
                            }
                        }
                        // пришло волокно , но  в точке идет по беспрерывному соединению кабеля
                    } else if ($con_unit["type"] == 'segment' && $node->type == "fiber" && $node->unit->id_segment == $con_unit["id"]) {
                        $next_con_unit = $units[0]["id"] == $con_unit["id"] ? $units[1] : $units[0];
                        $next_unit = (object)array("id_fiber" => $node->unit->id_fiber, "id_segment" => $next_con_unit["id"]);
                        array_push($children, $this->getFurtherConnectionForFiberUnit($next_unit, $node->point, $node->parent));
                    }
                }
            }
        } else if ($node->type == "splitter") {
            // сплиттер может уйти в другой сплиттер или в волокно
            $cons = array_filter($this->data->connections, function ($e) use ($node) {
                return $e->left_unit == $node->unit->id || $e->right_unit == $node->unit->id;
            });
            foreach ($cons as $con) {
                $units = array(array("type" => $con->left_unit_type, "id" => $con->left_unit),
                    array("type" => $con->right_unit_type, "id" => $con->right_unit));
                $next_unit = $units[0]["id"] == $node->unit->id ? $units[1] : $units[0];
                if ($next_unit["type"] == "fiber") {
                    $fiber_unit = $this->data->fiber[$next_unit["id"]];
                    $next_node = $this->getFurtherConnectionForFiberUnit($fiber_unit, $node->point, $node->parent);
                    array_push($children, $next_node);
                } else if ($next_unit["type"] == "splitter") {
                    $next_con_unit = $this->data->splitters[$next_unit["id"]];
                    if (!$next_con_unit->id_splitter == $node->unit->id_splitter)
                        $children = array_merge($children, $this->processSplitterConnection($next_con_unit, $node->point, $node->parent));
                }
            }

        }
        return $children;
    }

    function processSplitterConnection($splitter, $point, $parent)
    {
        $cons = array_filter($this->data->splitters, function ($e) use ($splitter) {
            return $e->id_splitter == $splitter->id_splitter && $e->input != $splitter->input;
        });
        $nodes = array();
        $splitter_info = ConnectionController::GetSplitterInfo($splitter->id_splitter);
        foreach ($cons as $con) {
            $p = $this->indexer;
            $this->tree->addItem($this->indexer++, $parent, array(
                    "id" => $splitter->id_splitter,
                    "type" => "splitter")
            );
            $attenuation = @$splitter_info["attenuation"][$splitter->input ? $con->number : $splitter->number];
            if (!$attenuation) $attenuation = $splitter_info["common_attenuation"];
            $this->tree->addPath($parent, $p, $splitter->id_splitter,
                array("input" => $splitter->number, "output" => $con->number, "attenuation" => $attenuation, "direct" => $splitter->input == 1),
                "splitter");
            array_push($nodes, new nextNode($point, "splitter", $con, $p));
        }
        return $nodes;
    }

    function addPointNode($segment, $point, $parent)
    {
        $p = $this->indexer;
        if ($point == $this->target) array_push($this->leafs, $p);
        else if ($point == $this->closestPoint)
            array_push($this->suggest_leafs, $p);

        else {
            
            $start = json_decode($this->data->points[$this->target]->location);
            $end = json_decode($this->data->points[$point]->location);
            $distance = solveDistance($start[0], $start[1], $end[0], $end[1]);
            if ($this->closestPoint == null || $distance < $this->shortestDistance) {
				$this->suggest_leafs = array();
				array_push($this->suggest_leafs, $p);
                $this->shortestDistance = $distance;
                $this->closestPoint = $point;
            }
        }
        $this->tree->addItem($this->indexer++, $parent, array(
                "id" => $point,
                "type" => "point"
            )
        );

        $this->tree->addPath($parent, $p, $segment->id, $this->getSegmentInfo($segment->id), "segment");
        return $p;
    }

    function getFurtherConnectionForFiberUnit($unit, $point, $parent)
    {
        $segment = $this->data->segments[$unit->id_segment];
        $next_point = $segment->start_point == $point ? $segment->end_point : $segment->start_point;
        $p = $this->addPointNode($segment, $next_point, $parent);
        return new nextNode($next_point, "fiber", $unit, $p);
    }

    function getSegmentInfo($id)
    {
        $segment = $this->data->segments[$id];
        $attenuation = $segment->fact_attenuation;
        $length = $segment->length;
        if (!$length) {
            $start = json_decode($this->data->points[$segment->start_point]->location);
            $end = json_decode($this->data->points[$segment->end_point]->location);
            $length = solveDistance($start[0], $start[1], $end[0], $end[1]);
        }
        if (!$attenuation)
            if ($segment->attenuation)
                $attenuation = $length * $segment->attenuation;
            else
                $attenuation = 0;
        return array("length" => $length, "attenuation" => $attenuation, "title" => $segment->title );
    }

    function getRouteFromNode($leaf)
    {
        $hops = array();
        $node_id = $leaf;
        $length = 0;
        $attenuation = 0;
        while ($node_id) {
            $node = $this->tree->getItem($node_id);
            $nodeType  = $node["data"]["type"];
            $welding = $this->tree->getLocal($node["id"]);
            if ($nodeType == "point") {
                $point = $this->data->points[$node["data"]["id"]];
                array_push($hops, array("type" => $nodeType, "id" => $node["data"]["id"],
                    "location" => $point->location, "title" => $point->title, "welding" => $welding));
            }

            if ($node["parent"] != 0) {
                $path_info = $this->tree->getPath($node["id"], $node["parent"]);
                $data = $path_info["data"];
                if ($path_info["type"] == "segment")
                    $length += $data["length"];
                $attenuation += $data["attenuation"];
                array_push($hops, array("type" => $path_info["type"], "id" => $path_info["id"], "data" => $data));
            }
            $attenuation += $welding;
            $node_id = $node["parent"];
        }
        $hops = array_reverse($hops);
        return array("route" => $hops, "length" => $length, "attenuation" => $attenuation);
    }

    function getRoutes()
    {
        $direct_paths = array();
        $suggest_path = array();
        foreach ($this->leafs as $leaf)
            array_push($direct_paths, $this->getRouteFromNode($leaf));

        if (count($direct_paths) == 0)
            foreach($this->suggest_leafs as $leaf)
                array_push($suggest_path, $this->getRouteFromNode($leaf));

        usort($direct_paths, "cmpRoutes");
        usort($suggest_path, "cmpRoutes");
        return array("direct" => $direct_paths, "suggest" => $suggest_path);
    }
}

function cmpRoutes($a, $b)
{
    if ($a["attenuation"] == $b["attenuation"]) return cmpRoutesLength($a, $b);
    else return ($a["attenuation"] < $b["attenuation"]) ? -1 : 1;
}

function cmpRoutesLength($a, $b)
{
    if ($a["length"] == $b["length"]) return 0;
    else return ($a["length"] < $b["length"]) ? -1 : 1;
}

function solveDistance($lat1, $lon1, $lat2, $lon2)
{
    $a = 6378137 - 21 * sin($lat1);
    $b = 6356752.3142;
    $f = 1 / 298.257223563;

    $p1_lat = $lat1 / 57.29577951;
    $p2_lat = $lat2 / 57.29577951;
    $p1_lon = $lon1 / 57.29577951;
    $p2_lon = $lon2 / 57.29577951;

    $L = $p2_lon - $p1_lon;

    $U1 = atan((1 - $f) * tan($p1_lat));
    $U2 = atan((1 - $f) * tan($p2_lat));

    $sinU1 = sin($U1);
    $cosU1 = cos($U1);
    $sinU2 = sin($U2);
    $cosU2 = cos($U2);

    $lambda = $L;
    $lambdaP = 2 * M_PI;
    $iterLimit = 20;

    while (abs($lambda - $lambdaP) > 1e-12 && $iterLimit > 0) {
        $sinLambda = sin($lambda);
        $cosLambda = cos($lambda);
        $sinSigma = sqrt(($cosU2 * $sinLambda) * ($cosU2 * $sinLambda) + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda));

        //if ($sinSigma==0){return 0;}  // co-incident points
        $cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
        $sigma = atan2($sinSigma, $cosSigma);
        $alpha = asin($cosU1 * $cosU2 * $sinLambda / $sinSigma);
        $cosSqAlpha = cos($alpha) * cos($alpha);
        $cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
        $C = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
        $lambdaP = $lambda;
        $lambda = $L + (1 - $C) * $f * sin($alpha) * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
    }

    $uSq = $cosSqAlpha * ($a * $a - $b * $b) / ($b * $b);
    $A = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
    $B = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));

    $deltaSigma = $B * $sinSigma * ($cos2SigmaM + $B / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM) - $B / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));

    $s = $b * $A * ($sigma - $deltaSigma);
    return $s / 1000;
}